package com.vains.config.security;

import com.vains.bean.Result;
import com.vains.constant.GlobalConstants;
import com.vains.constant.HttpStatusConstants;
import com.vains.filter.TokenAuthenticationFilter;
import com.vains.util.ServletUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;

/**
 * Security 配置类
 *
 * @author vains
 * @date 2021/4/3 1:23
 */
@Slf4j
@Order(1)
@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final TokenStore tokenStore;

    private final String loginUrl = "/login.html";

    private final PasswordEncoder passwordEncoder;

    private final UserDetailsService userDetailsService;

    private final RequestCache requestCache = new HttpSessionRequestCache();

    private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    /**
     * 注入密码解析器
     * @return 返回父类密码解析器
     * @throws Exception 获取失败时抛出
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().exceptionHandling().authenticationEntryPoint(this::authenticationEntryPoint);
            // 开启表单登录
        http.authorizeRequests(authorize ->
                authorize.antMatchers(GlobalConstants.AUTH_WHITE_LIST).permitAll().anyRequest().authenticated()
            );
        http.formLogin().loginPage(loginUrl).loginProcessingUrl("/login").successHandler(this::successHandler).permitAll();
        http.addFilter(new TokenAuthenticationFilter(authenticationManagerBean(), tokenStore));
    }

    /**
     *  oauth2 授权登录成功时触发
     * @param request 请求
     * @param response 响应
     * @param authentication 具体的异常
     * @throws IOException  重定向时可能会抛出
     */
    private void successHandler(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        // 取出缓存的request
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        if (savedRequest == null) {
            ServletUtils.writeDataByResponse(response, Result.error(HttpStatusConstants.UNAUTHORIZED, "登录失败!"));
            return;
        }
        // 获取重定向到登录之前的url
        String targetUrl = savedRequest.getRedirectUrl();
        URI savedUri = URI.create(targetUrl);
        // 获取请求路径和请求参数
        String path = savedUri.getRawPath();
        String query = savedUri.getQuery();
        // 替换ip为网关记录的url(由网关转发之后IP变为内网IP，所以要在网关处记录转发之前的域名)
        String redirectUrl = request.getHeader(GlobalConstants.BEFORE_REDIRECT_URI_KEY);
        redirectUrl = redirectUrl + path + "?" + query;
        log.info("URL: {}", redirectUrl);
        this.redirectStrategy.sendRedirect(request, response, redirectUrl);
    }

    /**
     * oauth2 授权登录失败时触发
     * @param request 请求
     * @param response 响应
     * @param e 具体的异常
     * @throws IOException 重定向时可能会抛出
     */
    private void authenticationEntryPoint(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        // 替换ip为网关记录的url(由网关转发之后IP变为内网IP，所以要在网关处记录转发之前的域名)
        String redirectUrl = request.getHeader(GlobalConstants.BEFORE_REDIRECT_URI_KEY);
        if (StringUtils.isEmpty(redirectUrl)) {
            log.warn("未获取到重定向的地址，直接重定向至：{} ...", loginUrl);
            redirectUrl = loginUrl;
        }
        redirectUrl += loginUrl;
        this.redirectStrategy.sendRedirect(request, response, redirectUrl);
    }

    @Override
    public void configure(WebSecurity web) {
        // 静态资源放行
        web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }

}
